console.log('start');
setTimeout(() => {
console.log('setTimeout');
});
Promise.resolve().then(function() {
console.log('promise');
});
console.log('end');
這段程式碼來自這個問題,會印出:
"start"
"end"
"promise"
"setTimeout"
基於昨天寫的 event loop 步驟,可能會對於這個結果有點意外,因為會以為順序是執行完同步程式碼後會先做一件 macrotask 再做 microtask,所以誤認為 'setTimeout' 會早於 'promise'。
針對這裡會先執行 microtask 這點有看到幾種說法,一種是如同 stack overflow 的回答所說,執行 JS script 本身也是一個 macrotask,所以 call stack 空了以後會執行 microtask。這篇文章有寫到比較具體的過程:
At the execution of any JS file, the JS engine wraps the contents in a function and associates the function with an event either start or launch. The JS engine emits the start event, the events are added to the task queue (as a macrotask).
另一類說法比較不像解釋,而是一開始就強調 microtask 優先順序較高,所以當 call stack 空了之後會先檢查 microtask queue,然後才檢查 macrotask。(但因為 event loop 不斷循環,所以個人理解這裡所謂的優先比較是循環從 microtask 起算的感覺。)
但無論是哪種,至少都不違背主要的原則:每次循環執行一件 macrotask,而 microtask 則會穿插在每個 macrotask 之間執行直到 microtask queue 為空。
console.log('start here');
const foo = () => (new Promise((resolve) => {
console.log('first promise constructor');
let promise1 = new Promise((resolve) => {
console.log('second promise constructor');
setTimeout(() => {
console.log('setTimeout');
resolve();
}, 0)
resolve('promise1')
})
resolve('promise0')
promise1.then(arg => {
console.log(arg);
})
}))
foo().then(arg => {
console.log(arg);
})
console.log('end here');
這是在《前端工程》裡看到的例子,會印出:
"start here"
"first promise constructor"
"second promise constructor"
"end here"
"promise1"
"promise0"
"setTimeout"
程式碼一長看起來就有點複雜,但還是維持同步 -> microtask -> macrotask 的順序。
現在看這種問題稍微不怕,而且思考有點依據,也算是了結關於 promise 的一樁心事,但我衷心希望面試可以多多討論原則就好。